import React, {useContext, useReducer} from "react";
import {shallowEqual} from "./equal";
import produce from 'immer';

export class HookRedux {
    constructor(allStore) {
        this.init(allStore)
    }
    value = {
        reducers: {},
        initStates: {},
    };

    initialed = false;

    init = (allStore) => {
        const initValue = Object.keys(allStore).reduce((s, v) => {
            const {reducer, state} = allStore[v];
            if(!reducer || !state) {
                return s
            }
            s.reducers[v] = reducer;
            s.initStates[v] = state;
            return s;
        }, {
            reducers: {},
            initStates: {}
        });

        // combined.reducers = this.getHooksReducer(combined.reducers)
        Object.assign(this.value, initValue)
        this.initialed = true;
    };

    getHooksReducer = (reduxStates, action) => {
        const {newModelState, model} = this.reducerExecutor(this.value.reducers, reduxStates, action)
        // just update one model one time
        return {
            [model]: newModelState,
        }
    };

    reducerExecutor = (reducers, reduxStates, action) => {
        const [model, type] = action.type.split('/');
        const reducer =  (reducers[model] && reducers[model][type]) || this.throwErr;
        const modelState = reduxStates[model] || {};
        const newModelState = produce(modelState, (draft) => reducer({
            state: draft,
            payload: action.payload,
            allStates: reduxStates
        }))
        return {
            newModelState,
            model,
        }
    };

    useRedux = () => {
        if (!this.initialed) {
            this.throwErr('please call init first')
        }
        const [states, dispatch] = useReducer(this.getHooksReducer, this.value.initStates);
        return { states, dispatch };
    };

    throwErr = (s) => {
        if (typeof s === 'string') {
            throw new Error(s)
        } else {
            throw new Error()
        }
    }
}

const StoreContext = React.createContext(null);
export const StoreProvider = StoreContext.Provider;
const memoizeSingleArg = (stateMapper) => {
    let props = {};
    let preProps = {};
    return (arg) => {
        const {states, dispatch} = arg;
        const newProps = stateMapper(states);
        if (!shallowEqual(props, newProps)) {
            preProps = props;
            props = newProps;
            if (typeof props !== 'object') {
                throw new Error('must return object')
            }
            console.log(preProps, newProps, 7777)
        }
        return {props, preProps, dispatch};
    };
};
export const connect = (stateMapper, actionMapper) => (Component) => {
    const MemoComponent = React.memo(Component);
    const memoizedMapState = memoizeSingleArg(stateMapper);
    let actions = null;
    const getActions = (dispatch) => {
        actions = Object.keys(actionMapper).reduce((s, v) => {
            Object.assign(s, {[v]: () => actionMapper[v](dispatch)})
            return s;
        }, {})
    };
    const getActionsOnce = (dispatch) => {
        if (actionMapper) {
            if (!actions) {
                return getActions(dispatch)
            }
            return actions;
        }
        return {}
    }
    return () => {
        // Declare your memoized mapState function
        // Get data from and subscribe to the store
        const state = useContext(StoreContext);
        const mapActions = getActionsOnce(state.dispatch)
        const mapState = memoizedMapState(state);
        return (
            <MemoComponent
                {...{
                    ...mapState.props,
                    preProps: mapState.preProps,
                    dispatch: mapState.dispatch,
                    ...mapActions,
                }}
            />
        )
    }
}

